package cn.ante.basic.service.impl;

import cn.ante.basic.exception.BusinessException;
import cn.ante.basic.jwt.JwtUtils;
import cn.ante.basic.jwt.RsaUtils;
import cn.ante.basic.jwt.UserInfo;
import cn.ante.basic.service.ILoginService;
import cn.ante.basic.utils.AjaxResult;
import cn.ante.basic.utils.HttpUtil;
import cn.ante.basic.utils.MD5Utils;
import cn.ante.basic.utils.StrUtils;
import cn.ante.basic.vo.BinderVo;
import cn.ante.basic.vo.LoginVo;
import cn.ante.basic.vo.RegisterVo;
import cn.ante.basic.vo.WechatVo;
import cn.ante.basic.constant.BaseContants;
import cn.ante.system.domain.Menu;
import cn.ante.system.mapper.MenuMapper;
import cn.ante.system.mapper.PermissionMapper;
import cn.ante.user.domain.Logininfo;
import cn.ante.user.domain.User;
import cn.ante.user.domain.Wxuser;
import cn.ante.user.mapper.LogininfoMapper;
import cn.ante.user.mapper.UserMapper;
import cn.ante.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.deser.impl.ManagedReferenceProperty;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private MenuMapper menuMapper;


    @Value("${jwt.rsa.pri}")
    private String jwtRsaPrivate;

    public HashMap<String, Object> loginSuccessJwtHandler(Logininfo logininfo){
        // 1.创建UserInfo对象，存储logininfo对象，并用于后续赋值
        UserInfo userInfo = new UserInfo();
        userInfo.setLogininfo(logininfo);
        // 2.判断是后台用户登录才需要设置菜单信息、权限信息，类型为0那么就是后台用户登录
        if(logininfo.getType() == 0){
            // 2.1.获取用户权限信息，我们只需要sn权限编码即可
            List<String> permissions = permissionMapper.loadOwnPermissionByLogininfoId(logininfo.getId());
            // 2.2.获取用户菜单信息
            List<Menu> menus = menuMapper.loadOwnMenuBylogininfoId(logininfo.getId());
            // 2.3.将菜单信息，权限信息放入到userinfo对象中
            userInfo.setPermissions(permissions);
            userInfo.setMenus(menus);
        }
        // 3.根据密钥文件名称获取到密钥
        PrivateKey privateKey = RsaUtils.getPrivateKey(this.getClass().getClassLoader().getResource(jwtRsaPrivate).getFile());
        // 4.根据密钥、用户信息、获取JWT加密串
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 30);
        // 5.按照正常逻辑我们直接返回token给前端即可，但是那样前端需要解密，我们使用简化版本JWT，还是返回用户信息
        // 封装token、用户信息、权限信息、菜单信息给前端
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("logininfo", logininfo);
        map.put("permissions", userInfo.getPermissions());
        map.put("menus", userInfo.getMenus());
        return map;
    }
    /**
    * @Title: adminLogin
    * @Description: 后台登录实现
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/7/7 16:02
    * @Parameters: [loginVo]
    * @Return java.util.Map<java.lang.String,java.lang.Object>
    */
    @Override
    public HashMap<String, Object> adminLogin(LoginVo loginVo) {
        // 判断参数是否为空
        String password = loginVo.getPassword();
        Integer type = loginVo.getType();
        String username = loginVo.getUsername();
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(username) || StringUtils.isEmpty(type)) {
            throw new BusinessException("参数不能为空");
        }
        // 根据传入的账号和类型进行查找
        Logininfo logininfo = logininfoMapper.loadByAccount(loginVo);
        // 查找到的账户信息为空，就抛出异常
        if (logininfo == null) {
            throw new BusinessException("账号或者密码错误，请重新输入");
        }

        // 如果账户存在，就判断输入的密码加盐加密后与数据库中的密码是否一致
        String passwordTmp = MD5Utils.encrypByMd5(logininfo.getSalt() + password);
        if (!logininfo.getPassword().equals(passwordTmp)) {
            throw new BusinessException("账号或者密码错误，请重新输入");
        }
//        ========================以下是使用redis存储登录信息的方法
//        // 当账号和密码都正确时候，登陆成功，将登陆成功后的信息作为redis的value,随机生成uuid作为value中的key
//        String token = UUID.randomUUID().toString();
//        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
//
//        // 将用户的敏感信息置为空
//        logininfo.setPassword("");
//        logininfo.setSalt("");
//
//        // 设置token和用户消息放进map中
//        Map<String, Object> map = new HashMap<>();
//        map.put("token",token);
//        map.put("logininfo",logininfo);
//        return map;
//    }
        // ====================以下是使用jwt的方法
        // 当账号和密码都正确时候，登陆成功，将登陆成功后的信息作为jwt串的value,随机生成uuid作为value中的key
        return loginSuccessJwtHandler(logininfo);
    }




    /**
    * @Description: 第三方微信登录实现，免密登录
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/7/10 18:31
    */
    @Override
    public AjaxResult wechatLogin(WechatVo wechatVo) {
        // 得到前端传过来的code
        String code = wechatVo.getCode();

        // 对code非空进行判断
        if (StringUtils.isEmpty(code)){
            throw new BusinessException("参数不能为空");
        }

        // 拼接请求获得access_token的地址，将appid/secret/url设置常量
        String tokenUrl = BaseContants.WechatCodeConstant.URL.replace("APPID",BaseContants.WechatCodeConstant.APPID)
                .replace("SECRET",BaseContants.WechatCodeConstant.SECRET)
                .replace("CODE",code);

        // 使用工具类，得到响应请求的一个json格式的字符串
        String jsonStr = HttpUtil.httpGet(tokenUrl);

        // 将json字符串解析为json对象
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);

        // 获取对象中的key值得到openid的值
        String openId = jsonObject.getString("openid");
        String accessToken = jsonObject.getString("access_token");

        // 通过openid去查找微信登录用户表中是否有这个微信用户
        Wxuser wxuser = wxuserMapper.loadByOpenId(openId);

        // 当微信用户不为空，且绑定了用户表时，表明该用户不是第二次登录，已有用户id.执行免密登录逻辑
        if (wxuser != null && wxuser.getUserId() != null){

            // 根据userid查询到logininfo的信息
            Logininfo logininfo = logininfoMapper.loadByUserId(wxuser.getUserId());

            // 执行免密登录逻辑，生成token,
//            String token = UUID.randomUUID().toString();
//            // 将token和logininfo保存在redis中
//            redisTemplate.opsForValue().set(token,logininfo,30,TimeUnit.MINUTES);
//
//            // 将敏感信息置空
//            logininfo.setPassword("");
//            logininfo.setSalt("");
//
//            // 将结果封装在map中，返回给前端
//            Map<String, Object> map = new HashMap<>();
//            map.put("token",token);
//            map.put("logininfo",logininfo);
//            return AjaxResult.me().setResultObj(map);
            HashMap<String, Object> map = loginSuccessJwtHandler(logininfo);
            return AjaxResult.me().setResultObj(map);
        }
        // 当用户为空的时候，告诉用户需要跳转一个绑定页面，让用户进行绑定,要携带参数，下个接口会使用
        StringBuffer str = new StringBuffer()
                .append("?accessToken=").append(jsonObject.getString("access_token")).append("&openId=").append(jsonObject.getString("openid"));

        // 返回失败的eAjaxResult并携带参数
        return AjaxResult.me().setSuccess(false).setResultObj(str);

    }

    /**
    * @Description: 第三方登录时，绑定用户业务
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/7/10 19:51
    */
    @Override
    @Transactional
    public HashMap<String, Object> wechatBinder(BinderVo binderVo) {

        String phone = binderVo.getPhone();
        String accessToken = binderVo.getAccessToken();
        String openId = binderVo.getOpenId();
        String verifyCode = binderVo.getVerifyCode();
        // 参数非空判断
        if(StringUtils.isEmpty(phone) ||
                StringUtils.isEmpty(verifyCode)||
                StringUtils.isEmpty(accessToken)||
                StringUtils.isEmpty(openId)){
            throw new BusinessException("参数不能为空，请填入参数！");
        }

        // 判断手机验证码，查看在redis中是否过期
        // 先将redis中key确定，然后查找reids中的value
        String smsCodekey = BaseContants.VerifyCodeConstant.BUSINESS_BINDER_PREFIX + phone;
        Object smsCodeValueTmp = redisTemplate.opsForValue().get(smsCodekey);
        if (smsCodeValueTmp == null){
            throw new BusinessException("验证码已过期，请重新输入");
        }
        String[] arr = smsCodeValueTmp.toString().split(":");
        String phoneCode = arr[1];
        // 判断输入验证码是否正确
        if (!phoneCode.equals(verifyCode)){
            throw new BusinessException("验证码错误，请重新输入");
        }

        // 验证码正确后，根据电话号查询用户是否存在user表中
        User user = userMapper.loadByPhone(phone);

        Logininfo logininfo = null;
        // 判断user是否存在
        if (user == null){
            // 初始化user
            user = init(phone);
            userMapper.add(user);
            // 根据user对象创建logininfo对象
            logininfo = user2Logininfo(user);
            logininfoMapper.add(logininfo);
            // 获取logininfo的主键设置到user中
            user.setLogininfoId(logininfo.getId());
            userMapper.update(user);
        }else{
            // 如果用户存在则表示此微信之前注册过账户
            user = userMapper.loadByLogininfoId(logininfo.getId());
        }
        // 根据token和openid获取到个人微信信息
        String wxUserUrl = BaseContants.WechatCodeConstant.OPENID
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        String sJson = HttpUtil.httpGet(wxUserUrl);
        Wxuser wxuser = JSONObject.parseObject(sJson, Wxuser.class);
        // 在wxuser中保存对象的id
        wxuser.setUserId(user.getId());
        // 保存微信用户信息
        wxuserMapper.add(wxuser);

        // 生成token保存到redis中
//        String token = UUID.randomUUID().toString();
//        redisTemplate.opsForValue().set(
//                token,
//                logininfo,
//                30,
//                TimeUnit.MINUTES
//        );
//        // 将用户敏感信息置空
//        logininfo.setPassword("");
//        logininfo.setSalt("");
//        // 并设置token和用户信息到map中
//        HashMap<String, Object> map = new HashMap<>();
//        map.put("token", token);
//        map.put("logininfo", logininfo);
        return loginSuccessJwtHandler(logininfo);
    }

    public User init(String phone){
        User user = new User();
        user.setPhone(phone);
        user.setUsername(phone);
        String salt = StrUtils.getComplexRandomString(32);
        String MD5Pwd = MD5Utils.encrypByMd5(salt + "1");
        user.setSalt(salt);
        user.setPassword(MD5Pwd);
        return user;
    }


    public Logininfo user2Logininfo(User user){
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user,logininfo);
        logininfo.setType(1);
        return logininfo;
    }
}
